Generics trong Java
Trong Java, Generic Methods và Wildcards là những tính năng mạnh mẽ giúp viết mã linh hoạt, tái sử dụng và an toàn về kiểu dữ liệu. Bài viết dưới đây sẽ giải thích chi tiết về khái niệm, cú pháp và cách sử dụng của chúng.
1. Generic Methods
1.1. Định nghĩa Generic Methods
Generic Methods là những phương thức có thể hoạt động với nhiều kiểu dữ liệu khác nhau mà không cần định nghĩa lại cho từng kiểu cụ thể. Nhờ đó, chúng ta có thể viết một đoạn mã chung, đồng thời đảm bảo an toàn kiểu (type safety) tại thời điểm biên dịch.
1.2. Cú pháp khai báo Generic Methods
Để khai báo một generic method, chúng ta cần thêm danh sách tham số kiểu (type parameter list) ngay trước kiểu trả về của phương thức. Cú pháp chung như sau:
public <T> T genericMethod(T param) {
// Xử lý và trả về giá trị kiểu T
return param;
}
<T>: là danh sách tham số kiểu, trong đóTlà một biến đại diện cho kiểu dữ liệu bất kỳ.T param: tham số đầu vào có kiểu T.T genericMethod(...): kiểu trả về của phương thức cũng là kiểu T.
1.3. Ví dụ về Generic Methods
Giả sử bạn muốn viết một phương thức hoán đổi giá trị của hai phần tử trong mảng. Với Generic Method, bạn có thể triển khai như sau:
public class GenericMethodExample {
// Generic method để hoán đổi hai phần tử trong mảng
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4};
System.out.println("Mảng ban đầu: " + java.util.Arrays.toString(intArray));
swap(intArray, 1, 3);
System.out.println("Mảng sau khi hoán đổi: " + java.util.Arrays.toString(intArray));
String[] strArray = {"apple", "banana", "cherry"};
System.out.println("Mảng ban đầu: " + java.util.Arrays.toString(strArray));
swap(strArray, 0, 2);
System.out.println("Mảng sau khi hoán đổi: " + java.util.Arrays.toString(strArray));
}
}
Trong ví dụ trên, phương thức swap có thể hoán đổi giá trị của mảng kiểu Integer hoặc String nhờ vào tham số kiểu <T>.
1.4. Lợi ích của Generic Methods
- Tái sử dụng mã: Bạn chỉ cần viết một lần, có thể sử dụng cho nhiều kiểu dữ liệu khác nhau.
- An toàn kiểu: Các lỗi liên quan đến kiểu dữ liệu được phát hiện tại thời điểm biên dịch, giúp tránh lỗi runtime.
- Tính linh hoạt: Cho phép kết hợp với các kiểu dữ liệu khác nhau, từ đó tăng khả năng mở rộng của chương trình.
2. Wildcards trong Generics
2.1. Định nghĩa Wildcards
Wildcards là ký hiệu đại diện cho một kiểu dữ liệu không xác định khi làm việc với generics. Chúng được biểu diễn bằng dấu chấm hỏi ?. Wildcards cho phép viết các phương thức, lớp hoặc interface có thể làm việc với các đối tượng của các kiểu khác nhau mà vẫn đảm bảo được an toàn kiểu.
2.2. Các loại Wildcards
Có ba loại wildcards phổ biến trong Java:
a. Unbounded Wildcard: ?
-
Ý nghĩa: Đại diện cho bất kỳ kiểu dữ liệu nào.
-
Sử dụng: Khi bạn không quan tâm đến kiểu cụ thể của các phần tử.
-
Ví dụ:
public static void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
b. Bounded Wildcard với extends: ? extends T
-
Ý nghĩa: Đại diện cho một kiểu không xác định nào đó, nhưng kiểu đó phải là
Thoặc là con củaT. -
Sử dụng: Khi bạn chỉ cần đọc dữ liệu từ danh sách mà không cần sửa đổi.
-
Ví dụ:
public static void processNumbers(List<? extends Number> list) {
for (Number number : list) {
System.out.println(number.doubleValue());
}
}Ở đây, danh sách có thể chứa các đối tượng kiểu
Integer,Float,Double,... vì tất cả đều kế thừa từNumber.
c. Bounded Wildcard với super: ? super T
-
Ý nghĩa: Đại diện cho một kiểu không xác định nào đó, nhưng kiểu đó phải là
Thoặc là cha củaT. -
Sử dụng: Khi bạn cần ghi dữ liệu vào danh sách.
-
Ví dụ:
public static void addIntegers(List<? super Integer> list) {
list.add(100);
list.add(200);
}Ở đây, danh sách có thể chứa các đối tượng kiểu
Integerhoặc của các lớp cha củaInteger(ví dụ:Number,Object).
2.3. Sự khác biệt giữa ? extends T và ? super T
? extends T:- Được sử dụng khi cần đọc dữ liệu từ một collection.
- Không cho phép thêm phần tử (ngoại trừ giá trị
null), vì không chắc chắn kiểu của phần tử sẽ là gì.
? super T:- Được sử dụng khi cần ghi dữ liệu vào collection.
- Có thể thêm phần tử kiểu T hoặc các kiểu con của T, vì đảm bảo rằng kiểu của danh sách là cha của T.
2.4. Ví dụ kết hợp Generic Methods và Wildcards
Giả sử bạn có một phương thức để in ra các phần tử của danh sách và bạn muốn nó hoạt động với bất kỳ kiểu dữ liệu nào:
public class WildcardExample {
// Generic method sử dụng unbounded wildcard để in ra danh sách
public static void printElements(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
public static void main(String[] args) {
List<Integer> intList = java.util.Arrays.asList(10, 20, 30);
List<String> strList = java.util.Arrays.asList("A", "B", "C");
System.out.println("In ra danh sách số:");
printElements(intList);
System.out.println("\nIn ra danh sách chuỗi:");
printElements(strList);
}
}
Trong ví dụ này, phương thức printElements sử dụng unbounded wildcard (?), cho phép nó in ra danh sách của bất kỳ kiểu dữ liệu nào mà không cần quan tâm đến kiểu cụ thể.
3. Tổng Kết
- Generic Methods giúp viết các phương thức có thể hoạt động với nhiều kiểu dữ liệu khác nhau mà vẫn đảm bảo an toàn kiểu, giúp tăng tính tái sử dụng và linh hoạt của mã nguồn.
- Wildcards cho phép chúng ta làm việc với các đối tượng của các kiểu khác nhau trong generics một cách an toàn và linh hoạt.
?(Unbounded wildcard): Dùng khi không quan tâm đến kiểu cụ thể.? extends T(Upper bounded wildcard): Dùng khi chỉ cần đọc dữ liệu từ collection.? super T(Lower bounded wildcard): Dùng khi cần ghi dữ liệu vào collection.
Nhờ vào Generic Methods và Wildcards, Java mang lại cho lập trình viên khả năng viết mã an toàn, tái sử dụng cao, đồng thời xử lý các tập hợp dữ liệu phức tạp một cách hiệu quả và linh hoạt. Đây là một trong những đặc tính quan trọng giúp Java trở thành một ngôn ngữ mạnh mẽ trong việc phát triển các ứng dụng lớn và có tính mở rộng cao.